﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.IO;
using ScrumTable.Common;
using ScrumTable.Common.FileSystem;
using ScrumTable.DL.Data.Generic.Properties;

#endregion


namespace ScrumTable.DL.Data.Generic.Cache
{
    /// <summary>
    /// This class wraps a team foundation attachment instance.
    /// </summary>
    public class CacheAttachment : CacheElementBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        internal const string UriPropertyName = "Uri";
        internal const string DescriptionPropertyName = "Description";

        private FilePoolHandle _localFile = null;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the associated WIQL iteration path.
        /// </summary>
        public string Description
        {
            get { return Values.TryGetTrackedValue<string>(DescriptionPropertyName); }
            set { TrackValueChange(DescriptionPropertyName, value); }
        }

        /// <summary>
        /// Gets/sets the attachment uri. The CacheAttachment takes over 
        /// file control (delete / create / ...).
        /// </summary>
        public Uri Uri
        {
            get { return Values.TryGetTrackedValue<Uri>(UriPropertyName); }
            set
            {
                if (value != null)
                {
                    if (!value.IsAbsoluteUri)
                    {
                        value = new Uri(Path.Combine(DataFolderUtil.GetApplicationDataFolder(), value.ToLocalPath()));
                    }

                    if (value.ContainsFilePath() && !File.Exists(value.ToLocalPath()))
                    {
                        throw new FileNotFoundException(string.Format(Resources.ExcCacheAttaFileNotFound, value));
                    }
                }

                Name = Path.GetFileName((value != null) ? value.ToLocalPath() : string.Empty);
                TrackValueChange(UriPropertyName, value);
                CreateLocalFile(value);
            }
        }

        /// <summary>
        /// Gets the attachment extension.
        /// </summary>
        /// <remarks>
        /// Do not use Path-based functionalyity inside/with the value of this property.
        /// They may throw InvalidPathCharacter exceptions because of the Name property may
        /// contain a user defined value with '?', ':', ... values.
        /// </remarks>
        public string Extension
        {
            get
            {
                if (!string.IsNullOrEmpty(Name))
                {
                    string fileNameExt = PathUtil.GetExtensionSave(Name);
                    if (!string.IsNullOrEmpty(fileNameExt)) { return fileNameExt; }
                }
                if (Uri != null)
                {
                    string localPath = Uri.ToLocalPath();
                    if (!string.IsNullOrEmpty(localPath)) { return Path.GetExtension(localPath); }
                }
                return string.Empty;
            }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new CacheAttachment instance. This constructor is used in
        /// the current library only.
        /// </summary>
        /// <param name="unqiueId">Specifies the unique id of an attachment.</param>
        /// <param name="name">Specifies the attachment name which includes the file extension.</param>
        /// <param name="id">Specifies the back end system identifier.</param>
        /// <param name="description">Specifies the attachment description (comment) field.</param>
        /// <param name="uri">Specifies the uri of an attachment.</param>
        internal CacheAttachment(
            string unqiueId,
            int id,
            string name,
            string description,
            Uri uri)
            : base(unqiueId, id, name, CacheIdentity.DefaultRevision)
        {
            Values.SetTrackedValue(UriPropertyName, null, uri);
            Values.SetTrackedValue(DescriptionPropertyName, null, description);
        }

        private CacheAttachment(string unqiueId)
            : base(unqiueId, CacheIdentity.DefaultId, CacheIdentity.DefaultUniqueId, CacheIdentity.DefaultRevision)
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>Returns the cloned object.</returns>
        public override CacheElementBase Clone()
        {
            CacheAttachment attachment = (CacheAttachment)base.Clone();
            attachment._localFile = _localFile;
            return attachment;
        }

        /// <summary>
        /// Creates a new clone instance of the current object.
        /// </summary>
        /// <returns>Returns the created clone instance.</returns>
        protected override CacheElementBase CreateCloneInstance()
        {
            return new CacheAttachment(UniqueId);
        }

        private void ImportFailedChanges(CacheAttachment lastChange)
        {
            if (!IsDirty)
            {
                _localFile = lastChange._localFile;
                Values.TrackChange(UriPropertyName, null, lastChange.Uri);
            }
        }

        private void ImportSavedChanges(CacheAttachment toMerge)
        {
            // get id property back from back end system
            AssignId(toMerge.Id);

            if (!IsDirty)
            {
                ImportFileFrom(toMerge);
            }
        }

        private void ForceImportChanges(CacheAttachment toMerge)
        {
            // get id property back from back end system
            AssignId(toMerge.Id);
            ImportFileFrom(toMerge);
        }

        private void ImportFileFrom(CacheAttachment toMerge)
        {
            if (toMerge.Uri != null && toMerge.Uri.TryGetIsFile())
            {
                // sync back if the data driver provided a new file
                // with another uri on the local harddrive (or network)
                _localFile = toMerge._localFile;
            }
            else
            {
                // the server specified either no attachment or
                // an attachment which is stored explicitly on the server
                _localFile = null;
            }
        }

        private void CreateLocalFile(Uri newValue)
        {
            if (newValue == null)
            {
                _localFile = null;
            }
            else if (newValue.ContainsFilePath()
                && (_localFile == null || _localFile.FullPath != newValue.ToLocalPath())
                && File.Exists(newValue.ToLocalPath()))
            {
                _localFile = FilePool.Default.CreateFrom(newValue);
            }
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        /// <summary>
        /// An error occurred while saving the given cache element. This means that
        /// the given element must be merged back (if there are no changes) into
        /// the cache tree.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which caused the error.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeError(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeError(toMerge, saveProgressFeedback);

            ImportFailedChanges((CacheAttachment)toMerge);
        }

        /// <summary>
        /// An element on server side has changed (which behaviour may caused an error). Its
        /// cache data must be invalidated in order to resynchronize with the server.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which data should be invalidated.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeInvalidateData(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeInvalidateData(toMerge, saveProgressFeedback);

            ForceImportChanges((CacheAttachment)toMerge);
        }

        /// <summary>
        /// An element has saved on server side. This means that data in the local storage may
        /// be invalidated if the change caused additional changes on server side. Workflow
        /// changes may cause additional value changes.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which data have ben saved.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeSaved(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeSaved(toMerge, saveProgressFeedback);

            ImportSavedChanges((CacheAttachment)toMerge);
        }

        #endregion
    }
}